﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MyUtils;
using MyUtils.Performance;
using XTrace;
using XTrace.Compatibility;
using XTrace.Registry;

namespace Test.MyUtils.Tracing
{
	[TestClass]
	public class Should_simply_trace_some_info
	{
		private static XTraceEvent _someEvent;
		public Should_simply_trace_some_info()
		{
			using(var tl = new TestListener())
			{
				XTrace.XTrace.Verbose(Guid.NewGuid().ToString(), Guid.NewGuid());
				_someEvent = tl.Events.First();
			}
		}

		class TestListener : XTraceListener
		{
			public readonly List<XTraceEvent> Events = new List<XTraceEvent>();

			public override void Event(XTraceEvent traceEvent)
			{
				Events.Add(traceEvent);
			}
		}

		[ClassInitialize]
		public static void CI(TestContext q)
		{
			ProfilingHelper.EnableProfiling();
		}

		[TestCleanup]
		public void C()
		{
			ProfilingHelper.FlushLog();
		}

		#region Utils

		static void CompareEvents(XTraceEvent expected, XTraceEvent actual)
		{
			foreach (var pro in expected.GetType().GetProperties())
			{
				// Console.WriteLine(pro.Name);
				var e = pro.GetValue(expected, null);
				var a = pro.GetValue(actual, null);
				if (a is DateTime)
				{
					var ed = (DateTime)e;
					var ad = (DateTime)a;
					var dif = ed - ad;
					if (dif < TimeSpan.Zero)
					{
						dif = -dif;
					}
					Assert.IsTrue(dif < TimeSpan.FromSeconds(.5));
				}
				else
				{
					Assert.AreEqual(e, a, "\r\n Expected: " + expected + "\r\n Actual: " + actual);
				}
				// Console.WriteLine("OK");
			}
		}

		static void AssertAndReport(int max, Stopwatch sw)
		{
			sw.Stop();
			var perOne = TimeSpan.FromTicks(sw.ElapsedTicks / max);
			var perOneReport = "Speed = " + perOne.ToSpanString() + " (" + perOne.TotalMilliseconds + ")";
			Console.WriteLine();

			var elapsed = sw.Elapsed.ToSpanString() + " (" + sw.ElapsedMilliseconds + "ms) " + perOneReport;
			Console.WriteLine(elapsed);

			Assert.IsTrue(sw.ElapsedMilliseconds < 2000, elapsed);
		}

		static XTraceEvent GetSomeEvent()
		{
			return _someEvent;
			// var str = Guid.Empty.ToString();
			// return XTraceEvent.();
			//return new XTraceEvent(str, XTraceLevel.Verbose, str, str, typeof(Should_simply_trace_some_info).Assembly);
		}

		class FakeListener : XTraceListener
		{
			public XTraceEvent LastEvent;
			public override void Event(XTraceEvent traceEvent)
			{
				LastEvent = traceEvent;
			}
		}

		static object[] GenerateParameters(MethodInfo mi)
		{
			var pars = new List<object>();
			foreach (var parameterInfo in mi.GetParameters())
			{
				if (parameterInfo.ParameterType == typeof(XTraceLevel))
				{
					pars.Add((XTraceLevel)((int)XTraceLevel.Verbose).Random());
				}
				else if (parameterInfo.ParameterType == typeof(object))
				{
					pars.Add(Guid.NewGuid().ToString());
				}
				else if (parameterInfo.ParameterType == typeof(bool))
				{
					pars.Add(true);
				}
				else if (parameterInfo.ParameterType == typeof(object[]))
				{
					pars.Add(new[] { "1", "2", "3", });
				}
				else if (parameterInfo.ParameterType == typeof(string))
				{
					pars.Add(Guid.NewGuid().ToString());
				}
				else if (parameterInfo.ParameterType == typeof(Exception))
				{
					try
					{
						throw null;
					}
					catch (Exception ex)
					{
						pars.Add(ex);
					}
				}
				else
				{
					Assert.Inconclusive(parameterInfo.ParameterType.Name);
				}
			}
			return pars.ToArray();
		}

		#endregion

		[TestMethod]
		public void Should_log_some_info()
		{
			using (new BridgeFromXTraceToTrace())
			{
				using (new ConsoleXTraceListener())
				{
					//using (new TextFileXTraceListener(Path.Combine(Path.GetTempPath(), "sample.csv"), false))
					{
						using (var listener = new TestListener())
						{
							var source = new XTraceSource("Test");
							source.Information("test", "someinfo");

							Assert.AreEqual(1, listener.Events.Count);
							Assert.AreEqual("someinfo", listener.Events[0].AdditionalInformation);
						}
					}
				}
			}
		}


		[TestMethod]
		public void Should_serialize_and_deserialize_xml()
		{
			var ev = GetSomeEvent();
			var xml = ev.Serialize();
			Console.WriteLine(xml);
			var act = XTraceEvent.Deserialize(xml);
			CompareEvents(ev, act);
		}

		[TestMethod]
		public void Should_serialize_fast()
		{
			var sw = Stopwatch.StartNew();
			const int max = 20000;
			var ev = GetSomeEvent();
			var src = new XTraceSource("pref test");
			using (var log = new StructuredTextFileXTraceListener(Path.Combine(Path.GetTempPath(), "loggingPerformanceTest_123123")))
			{
				using (P.S("All"))
				{
					for (int i = 0; i < max; i++)
					{
						using(P.S("Serialize"))
						{
							var q = ev.Serialize();
						}
					}
				}
				sw.Stop();
			}
			AssertAndReport(max, sw);
		}

		[TestMethod]
		public void Should_serialize_errors_fast()
		{
			var str = Guid.NewGuid().ToString();

			var sw = Stopwatch.StartNew();
			const int max = 5000;
			for (int i = 0; i < max; i++)
			{
				var data = GetSomeEvent().Serialize();
			}
			sw.Stop();

			AssertAndReport(max, sw);
		}

		[TestMethod]
		public void Should_deserialize_fast()
		{
			var str = Guid.NewGuid().ToString();
			var data = GetSomeEvent().Serialize();

			var sw = Stopwatch.StartNew();
			const int max = 20000;
			for (int i = 0; i < max; i++)
			{
				var ev = XTraceEvent.Deserialize(data);
			}
			sw.Stop();

			AssertAndReport(max, sw);
		}

		[TestMethod]
		public void Should_deserialize_errors_fast()
		{
			var str = Guid.NewGuid().ToString();

			var sw = Stopwatch.StartNew();
			const int max = 5000;
			for (int i = 0; i < max; i++)
			{
				var data = GetSomeEvent().Serialize();
				var ev = XTraceEvent.Deserialize(data);
			}
			sw.Stop();

			AssertAndReport(max, sw);
			Assert.Inconclusive();

		}

		[TestMethod]
		public void Should_write_events_to_deserializable_format()
		{
			string fn=null;
			try
			{
				using (var lis = new StructuredTextFileXTraceListener("test"+Guid.NewGuid(),RelativePath.CurrentDir))
				{
					var trace = new XTraceSource("asd");
					trace.Information("test1");
					trace.Warning("asd");
					fn = lis.ActualCurrentFileName;
				}
				Console.WriteLine(File.ReadAllText(fn));
			}
			finally
			{
				if (fn != null)
				{
					File.Delete(fn);
				}
			}
			Assert.Inconclusive();
		}

		[TestMethod]
		public void Should_register_file()
		{
			using (new StructuredTextFileXTraceListener("sample", RelativePath.CurrentDir))
			{
				XTrace.XTrace.Verbose("ensure file created");
				var path = SourcesProvider.RegPath;
				Assert.IsTrue(path.StartsWith(Environment.GetEnvironmentVariable("PROGRAMDATA")), path);

				var lines = File.ReadAllLines(path);
				Assert.IsTrue(lines.Any(x => x.Contains("sample_1.xlog")));
			}
		}

		[TestMethod]
		public void Should_register_once()
		{
			File.Delete(SourcesProvider.RegPath);
			using (new StructuredTextFileXTraceListener(Path.GetFullPath("sample1")))
			{
				XTrace.XTrace.Verbose("ensure file created");
			}
			using (new StructuredTextFileXTraceListener(Path.GetFullPath("sample1")))
			{
				XTrace.XTrace.Verbose("ensure file created");
			}
			var lines = File.ReadAllLines(SourcesProvider.RegPath);
			Assert.AreEqual(1, lines.Length);
		}

		[TestMethod]
		public void Should_register_several()
		{
			File.Delete(SourcesProvider.RegPath);
			using (new StructuredTextFileXTraceListener(Path.GetFullPath("sample1")))
			{
				XTrace.XTrace.Verbose("ensure file created");

			}
			using (new StructuredTextFileXTraceListener(Path.GetFullPath("sample1")))
			{
				XTrace.XTrace.Verbose("ensure file created");

			}
			using (new StructuredTextFileXTraceListener(Path.GetFullPath("sample2")))
			{
				XTrace.XTrace.Verbose("ensure file created");

			}
			var lines = File.ReadAllLines(SourcesProvider.RegPath);
			Assert.AreEqual(2, lines.Length);
		}

		[TestMethod]
		public void Should_write_something_by_any_method()
		{
			// new BridgeFromXTraceToTrace();

			using (var lastEvent = new FakeListener())
			{
				using (var ls = new StructuredTextFileXTraceListener("Should_write_something_by_any_method", RelativePath.CurrentDir))
				{
					ls.ForceInit();
					using (var readerStream = File.Open(ls.ActualCurrentFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
					{
						using (var reader = new StreamReader(readerStream))
						{
							var sut = new XTraceSource("sut");
							var source = new XTraceSource("Test");
							foreach (var mi in typeof(XTraceSource).GetMethods())
							{
								Console.WriteLine(mi.Name);
								if (mi.DeclaringType != typeof(XTraceSource))
								{
									continue;
								}
								if (mi.Name.StartsWith("get_") || mi.Name.StartsWith("set_") || mi.Name == "AddBundle")
								{
									continue;
								}
								var eba = mi.Attribute<EditorBrowsableAttribute>();
								if (eba != null)
								{
									if(eba.State==EditorBrowsableState.Never)
									{
										continue;
									}
								}
								var pars = GenerateParameters(mi);
								source.Information("Call", "name:{0} params:{1}", mi
.Name, pars.Select(x=>x.ToString()).Join(", "));
								var ev = lastEvent.LastEvent;
								var line = reader.ReadLine();
								var rest = XTraceEvent.Deserialize(line);
								CompareEvents(ev, rest);


								mi.Invoke(sut, pars);
								ev = lastEvent.LastEvent;
								line = reader.ReadLine();
								rest = XTraceEvent.Deserialize(line);
								CompareEvents(ev, rest);
							}
						}
					}
				}
			}
		}
	}
}
